import * as THREE from 'three';

import Stats from 'three/addons/libs/stats.module.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { getRandomInt, worldToScreen } from '@/core/tooler';
import { Line2 } from 'three/examples/jsm/lines/Line2.js'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js'
import Matter from "matter-js"
import { initConn } from "./Connection"
import { Bus, CONN_ACTIVITY } from './Bus';

initConn();

//立方球和虚线动画和物理引擎
//使用推送数据
//点击添加力
//添加约束

let group;
let topGroup;
let container, stats;
let camera, scene, renderer;
let topScene;
let selectedObject = null;
let overlay;

let addressList: any[] = [];
let actionList: any = [];

let pointMap: any = {};
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();

let matLine: any;
let matLineSelected:any;
// let linewidth = 4;

const config = {
    pointerColor: 0xafd516,
    eoxColor: 0xf5ab35,
    hoverColor: 0xffffff,
    lineColor: 0x909090,
    selectedLineColor: 0x00ff00,
    linewidth: 3,
    pointerSize: 30
}

let lastColor = 0;

let linesMap: any = {};
let physicsMap: any = {};
// let lineList = [];
var Engine = Matter.Engine,
    // Render = Matter.Render,
    Runner = Matter.Runner,
    Bodies = Matter.Bodies,
    Composite = Matter.Composite,
    Constraint = Matter.Constraint;

// create an engine
var engine = Engine.create();
engine.gravity.y = 0;

function initPhysics() {
    // create runner
    var runner = Runner.create();

    // run the engine
    Runner.run(runner, engine);
}


export function init(canvas: any, $overlay: any) {
    overlay = $overlay;
    container = document.body;

    initPhysics();

    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 20000);
    camera.position.z = 5000;

    const controls = new OrbitControls(camera, container);
    controls.minDistance = 100;
    controls.maxDistance = 30000;
    controls.enableRotate = false;

    scene = new THREE.Scene();
    scene.fog = new THREE.FogExp2(0x000000, 0.0004);

    topScene = new THREE.Scene();
    // topScene.fog = new THREE.FogExp2(0x000000, 0.0004);

    group = new THREE.Group();
    scene.add(group);

    topGroup = new THREE.Group();
    topScene.add(topGroup);

    renderer = new THREE.WebGLRenderer({ antialias: true, canvas });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.autoClear = false;

    stats = new Stats();
    container.appendChild(stats.dom);

    window.addEventListener('resize', onWindowResize);
    document.addEventListener('pointermove', onPointerMove);
    document.addEventListener('click', onClick);

    let light = new THREE.DirectionalLight(0xffffff, 0.8);
    light.position.set(200, 200, 100);
    scene.add(light)

    matLine = new LineMaterial({
        color: config.lineColor,
        linewidth: config.linewidth, // in world units with size attenuation, pixels otherwise
        vertexColors: false,
        //resolution:  // to be set by renderer, eventually
        dashed: true,
        alphaToCoverage: false,
        worldUnits: true,
        opacity: 1,
        dashSize: 3,
        gapSize: 5,
    });

    matLineSelected = new LineMaterial({
        color: config.selectedLineColor,
        linewidth: config.linewidth * 2, // in world units with size attenuation, pixels otherwise
        vertexColors: false,
        //resolution:  // to be set by renderer, eventually
        dashed: true,
        alphaToCoverage: false,
        worldUnits: true,
        opacity: 1,
        dashSize: 3,
        gapSize: 5,
    });


    // addView(getFakeData(50, 60));

    // setInterval(() => {
    //     addView(getFakeData(10, 100));
    // }, 3000);


    // let t = 0;
    Bus.on(CONN_ACTIVITY, obj => {
        if (Array.isArray(obj)) {
            addView(obj.slice(0, 300));
        }
        else {
            // if (++t < 2) {
            addView([obj]);
            // }
        }

    });

    animate();
}

function addView(items: any) {
    const data = items.map((item: any) => {
        return {
            id: item.transactionHash,
            out: item.fromTag || item.address,
            in: item.toTag || item.toAddress,
            origin: item,
        }
    });
    // actionList.push(...data);
    const points = getAddress(data);
    console.log("data length", data.length, "points length", points.length)
    addPoints(points);

    const lines = getLines(data);
    addCoinLines(lines);

    removeView();
}

function removeView() {
    if (addressList.length > 60) {
        let id = addressList.shift()
        let mesh = pointMap[id];
        topGroup.remove(mesh);
        delete pointMap[id];

        for (let i in linesMap) {
            let { name, from, to } = linesMap[i];
            if (from == mesh || to == mesh) {
                topGroup.remove(linesMap[i].mesh)
                delete linesMap[i];
                console.log("删除", i)

                console.log("actionList length", actionList.length);
                actionList = actionList.filter(i => i != name);
                console.log("actionList length", actionList.length)
            }
        }

    }
}

function getAddress(list: any[]) {
    let aim: any[] = [];
    list.forEach(item => {
        if (addressList.includes(item.in) == false) {
            aim.push({
                id: item.in,
                type: item.origin.toAddressType
            })
            addressList.push(item.in);
        }
        if (addressList.includes(item.out) == false) {
            aim.push({
                id: item.out,
                type: item.origin.fromAddressType
            })
            addressList.push(item.out);
        }
    })
    return aim;
}

function getLines(list: any[]) {
    let aim: any[] = [];
    list.forEach(item => {
        var n = getNames(item);
        if (actionList.includes(n) == false) {
            let obj = {
                name: n,
                id: item.id,

                to: item.in,
                from: item.out,
                toObj: pointMap[item.in],
                fromObj: pointMap[item.out],
                origin: item.origin
            };
            aim.push(obj)
            // lineList.push(obj);
            actionList.push(n);
        }
        else {
            console.log("重复线", n, item)
        }

    })
    // console.log(actionList)
    return aim;
}

function getNames(obj: any) {
    var list: string[] = [obj.out, obj.in];
    list.sort();
    return list.join("-")
}


function addPoints(points: any[]) {
    // const sprite = new THREE.TextureLoader().load('./asset/img/pot.png');
    // const map:any = {};
    for (let i = 0; i < points.length; i++) {
        // const pot = new THREE.Sprite(new THREE.SpriteMaterial({ color: config.pointerColor, map: sprite, sizeAttenuation: false, transparent: true, fog: true }));
        let { id, type } = points[i];
        let r = type == "eoa" ? config.pointerSize : config.pointerSize * 3;
        let color = type == "eoa" ? config.eoxColor : config.pointerColor;
        let geo = new THREE.SphereGeometry(r, 8, 8);
        let mat = new THREE.MeshBasicMaterial({ color: new THREE.Color(color) });
        let mesh = new THREE.Mesh(geo, mat);
        let size = 200;
        let item = mesh;
        let x = getRandomInt(-size, size);
        let y = getRandomInt(-size, size);
        // let z = getRandomInt(-size, size);
        let z = 0;

        let cir = Bodies.circle(x, y, r * 1.5);
        Composite.add(engine.world, [cir]);

        physicsMap[id] = cir;

        mesh.userData.body = cir;
        // let r = getRandomInt(1, 6) * 0.01;
        item.position.set(x, y, z);
        // item.scale.set(r, r, r);
        topGroup.add(item);
        pointMap[id] = item;
    }
    // return pointMap;
}

function addLines(p1: THREE.Vector3, p2: THREE.Vector3, two = false) {
    // let ary = [];
    // if (two) {
    //     let ofs = config.linewidth / 2;
    //     p1.add(new THREE.Vector3(ofs, -ofs, 0))
    //     p2.add(new THREE.Vector3(ofs, -ofs, 0))
    //     ary.push(addLine(p1, p2));
    //     let p3 = p1.clone().add(new THREE.Vector3(config.linewidth, -config.linewidth, 0));
    //     let p4 = p2.clone().add(new THREE.Vector3(config.linewidth, -config.linewidth, 0));
    //     ary.push(addLine(p4, p3));
    // }
    // else {
    //     ary.push(addLine(p1, p2));
    // }
    // return ary;

    return [addLine(p1, p2)];

    // addPot(p1)
    // addPot(p2)
}

function addLine(p1: THREE.Vector3, p2: THREE.Vector3) {
    // const positions = [];
    // positions.push(p1.x, p1.y, p1.z)
    // positions.push(p2.x, p2.y, p2.z)
    // const geometry = new LineGeometry();
    // geometry.setPositions(positions);
    // let line = new Line2(geometry, matLine);
    // line.computeLineDistances();
    // line.scale.set(1, 1, 1);
    // topGroup.add(line);
    // return line;

    const geometry = new LineGeometry();
    // geometry.setPositions(positions);
    let line = new Line2(geometry, matLine);
    // line.computeLineDistances();
    // line.scale.set(1, 1, 1);
    topGroup.add(line);
    return line;
}


function addCoinLines(lines: any[]) {
    // console.log(lines, "lines");
    // const material = new THREE.LineDashedMaterial({ color: 0xffffff, transparent: true, opacity: 0.4, dashSize: 4, gapSize: 2 });
    lines.forEach(item => {
        // const toObj = pointMap[item.to];
        // const fromObj = pointMap[item.from];
        const { toObj, fromObj, from, to } = item;
        

        var constraint = Constraint.create({
            bodyA: physicsMap[from],
            pointA: { x: 0, y: 0 },
            bodyB: physicsMap[to],
            pointB: { x: 0, y: 0 },
            stiffness: 0.001
        });

        Composite.add(engine.world, [constraint]);

        const ary = addLines(toObj.position.clone(), fromObj.position.clone(), false);
        ary.forEach(line => {
            linesMap[item.id] = {
                name: item.name,
                mesh: line,
                from: fromObj,
                to: toObj,
                origin: item.origin
            }
        })

    })
}

// 更新 DOM 元素的位置
function updateOverlayPosition() {
    if (!selectedObject) {
        overlay.style.transform = `translate(-200%, -200%)`;
        return;
    }
    const vector = worldToScreen(selectedObject, camera);
    const widthHalf = window.innerWidth / 2;
    const heightHalf = window.innerHeight / 2;
    const x = (vector.x * widthHalf) + widthHalf;
    const y = -(vector.y * heightHalf) + heightHalf;
    overlay.style.transform = `translate(-50%, -50%) translate(${x}px, ${y}px)`;
}

function onClick(e) {
    let find = getHitObject(e)
    if (find) {
        selectedObject = find;
        // selectedObject.userData.body;
        let body = selectedObject.userData.body;
        Matter.Body.applyForce(body, { x: body.position.x, y: body.position.y }, { x: 0.1, y: 1 });
        // lastColor = selectedObject.material.color.getHex();
        // console.log("lastColor", lastColor)
        // selectedObject.material.color.set(config.hoverColor);
    }
    for (let i in linesMap) {
        let { mesh, from, to } = linesMap[i];
        if(from == selectedObject || to == selectedObject){
            console.log("mesh", mesh);
            mesh.material = matLineSelected;
        }
        else{
            mesh.material = matLine;
        }
    }
    
}

function getHitObject(event: any) {
    if (selectedObject) {
        selectedObject.material.color.set(lastColor);
        selectedObject = null;
    }

    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
    pointer.y = - (event.clientY / window.innerHeight) * 2 + 1;

    raycaster.setFromCamera(pointer, camera);

    const intersects = raycaster.intersectObject(topGroup, true);

    if (intersects.length > 0) {
        // if(event.type != "pointermove"){
        //     console.log("得到", intersects[0].object)
        // }

        const res = intersects.filter(function (res) {
            return res && res.object;
        })[0];

        if (res && res.object) {
            for (let i in pointMap) {
                if (pointMap[i] == res.object) {
                    overlay.innerText = i;
                    res.object.name = i;
                    return res.object;
                }
            }
        }

    }
}

function onPointerMove(e) {
    let find = getHitObject(e)
    if (find) {
        selectedObject = find;
        lastColor = selectedObject.material.color.getHex();
        // console.log("lastColor", lastColor)
        selectedObject.material.color.set(config.hoverColor);
    }

}

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate() {
    requestAnimationFrame(animate);
    stats.update();
    updateOverlayPosition();
    render();
}

function render() {
    const time = Date.now() * 0.001;

    for (let i in pointMap) {
        let mesh = pointMap[i];
        let p = physicsMap[i].position;
        mesh.position.set(p.x, p.y, 0);
    }

    for (let i in linesMap) {
        let { mesh, from, to } = linesMap[i];
        let ps = [];
        let p1 = from.position;
        let p2 = to.position;
        ps.push(p1.x, p1.y, p1.z);
        ps.push(p2.x, p2.y, p2.z);

        mesh.geometry.setPositions(ps);
        mesh.computeLineDistances();
    }

    // group.rotation.y = time * 0.03;
    // topGroup.rotation.y = time * 0.03;
    matLine.resolution.set(window.innerWidth, window.innerHeight);
    matLine.dashOffset += 0.1;
    renderer.clear();
    renderer.render(scene, camera);
    renderer.clearDepth();
    renderer.render(topScene, camera);
}